package com.puzzlebazar.server;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Blob;
import com.puzzlebazar.client.puzzle.model.PuzzleModel;
import com.puzzlebazar.client.puzzleinfo.NewPuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo.Status;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo.Validity;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class PuzzleInfoJdo {

	@PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String key;

    @Persistent
    private String title = "";
    @Persistent
    private String genre = "";
    @Persistent
    private String size = "";
    @Persistent
	private String status = "";
    @Persistent
	private String validity = "";
	@Persistent
	private Date dateCreated = null;
	@Persistent
	private Date dateEdited = null;
	@Persistent
	private Date datePublished = null;
	@Persistent
	private boolean hasModelInDatastore = false;
	@Persistent
	private boolean tutorial = false;
	@Persistent
	private Blob puzzleModel = null;
	@Persistent
	private String authorEmail = null; // A key into UserInfoJdo
	@Persistent
	private String tutorialHistoryKey; // A key into HistoryInfoJdo
	    
    /**
     * @param destination The PuzzleInfo object to populate with this object content
     * @param loadModel True if the puzzleModel should be loaded. If false, 
     *                  destination.puzzleModel will not be modified
     */
    public void to( PuzzleInfo destination, boolean loadModel ) {
    	destination.setKey(key);
    	destination.setTitle(title);
    	destination.setGenre(genre);
    	destination.setSize(size);
    	try {
    		destination.setStatus( Status.valueOf(status) );
    	}
    	catch( Exception e ) {
    		destination.setStatus( Status.UNKNOWN );
    	}
    	try {
    		destination.setValidity( Validity.valueOf(validity) );
    	}
    	catch( Exception e ) {
    		destination.setValidity( Validity.UNKNOWN );
    	}
    	destination.setDateCreated( dateCreated );
    	destination.setDateEdited( dateEdited );
    	destination.setDatePublished( datePublished );
    	destination.setDifficulty( 0 );
    	destination.setQuality( 0 );
    	destination.setAuthorEmail( authorEmail );
    	destination.setAuthorUsername( authorEmail ); // TODO wrong username
    	destination.setTutorial( tutorial );
    	destination.setHasModelInDatastore( hasModelInDatastore );
    	if( loadModel ) {
			destination.setPuzzleModel( getPuzzleModel() );
    	}
    	destination.setTutorialHistoryKey(tutorialHistoryKey);
    }

    /**
     * Create a new puzzle based on the information passed
     * 
     * @param source The object containing all the information for the newly created puzzle
     */
    public void create( NewPuzzleInfo source ) throws Exception {
    	title = source.getTitle();
    	genre = source.getGenre();
    	size = source.getSize();
    	status = Status.PRIVATE.toString();
    	validity = Validity.UNKNOWN.toString();
    	dateCreated = new Date();
    	dateEdited = null;
    	datePublished = null;
    	hasModelInDatastore = false;
    	puzzleModel = null;
    	authorEmail = source.getAuthorEmail();
    	tutorial = source.isTutorial();
    }
	
	/**
	 * @param title The new title for this puzzle
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * @param status The new status for this puzzle
	 */
	public void setStatus(String status) {
		this.status = status;	
	}

	/**
	 * Attach the passed model to the current puzzle info. This will potentially
	 * modify size and validity.
	 * 
	 * @param model The model to attach
	 */
	public void attachPuzzleModel(PuzzleModel model) {
		assert( model != null );
		size = model.getSize().toSizeString();
    	validity = updateValidity( Validity.UNKNOWN, model ).toString();
    	hasModelInDatastore = true;
    	setPuzzleModel(model);
	}

	/**
	 * Checks the validity of this puzzle and updates its validity status based on the result
	 *
	 * @param validity The current validity state of the puzzle
	 * @param puzzleModel The puzzle model to evaluate
	 * @return The new validity state of this puzzle
	 */
	private Validity updateValidity( Validity validity, PuzzleModel puzzleModel ) {
		if( puzzleModel != null && puzzleModel.isSolutionValid(null, null) ) {
			if( validity != Validity.UNIQUE_SOLUTION &&
				validity != Validity.MULTIPLE_SOLUTIONS )
				validity = Validity.ONE_SOLUTION;
		} else {
			if( validity != Validity.NO_SOLUTION )
				validity = Validity.SOLUTION_UNKNOWN;
		}
		return validity;
	}


    /**
     * @return A newly created PuzzleModel object corresponding  to the one attached to this JDO, null if an error occured
     */
    private PuzzleModel getPuzzleModel() {
    	if( puzzleModel == null ) return null;
    	PuzzleModel result = null;
		try {
	    	ByteArrayInputStream modelByteStream = new ByteArrayInputStream(puzzleModel.getBytes());
	    	ObjectInputStream modelStream = new ObjectInputStream(modelByteStream);
	    	result = (PuzzleModel)modelStream.readObject();
	    	modelStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
     * @param puzzleModel The puzzle model to attach to this JDO
     */
    private void setPuzzleModel(PuzzleModel puzzleModel) {
		try {
			ByteArrayOutputStream modelByteStream = new ByteArrayOutputStream(512);
	    	ObjectOutputStream modelStream = new ObjectOutputStream(modelByteStream);
	    	modelStream.writeObject( puzzleModel );
	    	this.puzzleModel = new Blob(modelByteStream.toByteArray());
	    	modelStream.close();
		} catch (Exception e) {
			this.puzzleModel = null;
			e.printStackTrace();
		}
	}

	/**
	 * Set the edited date to right now
	 *
	 * @param date The date to set, null to reset
	 */
	public void setDateEdited( Date date ) {
		dateEdited = date;
	}

	/**
	 * Set the published date
	 *
	 * @param date The date to set, null to reset
	 */
	public void setDatePublished( Date date ) {
		datePublished = date;
	}

	/**
	 * Sets the history attached with this tutorial (null if it's not a tutorial
	 * 
	 * @param tutorialHistoryKey The key to the HistoryInfo of the tutorial attached
	 */
	public void setTutorialHistoryKey(String tutorialHistoryKey) {
		this.tutorialHistoryKey = tutorialHistoryKey;
	}
	/**
	 * @return The key associated with this puzzle
	 */
	public String getKey() {
		return key;
	}

}
